Иногда бывает необходимо сделать копию объекта. Если при этом копируются объекты, которые содержат другие объекты или указатели на них, то для корректной реализации этого процесса необходимо понимать его особенности. Ниже мы сравним копирование указателя, поверхностное почленное копирование и детальное копирование. Мы увидим, что в зависимости от реализации интерфейса ICloneable, используемого для копирования объектов, существует возможность как поверхностного, так и детального копирования.
Напомним, что в .NET Framework есть значащие типы данных и ссылочные типы.
Значащие типы данных представляют собой непосредственно данные, а ссылочные
типы всего лишь указывают местонахождение данных. Если значение одной переменной-указателя
скопировать в другую переменную того же типа, обе переменные будут указывать
на один и тот же объект. Если с помощью первого указателя этот объект будет
изменен, то изменится и объект, на который ссылается второй указатель. Иногда
требуется именно такое поведение, а иногда другое.
Интерфейс ICloneable является исходным и имеет единственный метод— Clone (Клон). Данный метод может быть реализован для выполнения как поверхностного, так и детального копирования, но указатель на Object (Объект), возвращаемый методом, должен указывать на объект того же (или совместимого) типа, для которого реализован интерфейс ICloneable. Обычно метод Clone (Клон) реализуют так, чтобы он создавал новый объект. Однако бывает, например в случае класса String (Строка), что метод Clone (Клон) просто возвращает указатель на исходный объект.
_gc _interface ICloneable
// сборщик мусора - ICloneable
{
Object* Clone(); // Клон
};
Поверхностная и детальная копии
Неуправляемые структуры и классы в C++ автоматически реализуют почленное копирование
содержимого, которое будет актуальным до тех пор, пока не будет произведена
подмена конструктора копирования. Почленное копирование называют также поверхностным
копированием. В базовом классе Object (Объект) также есть защищенный (protected)
метод, MemberwiseClone, выполняющий почленное копирование управляемых структур
или классов.
Если среди членов структуры или класса есть указатели, такое почленное копирование может быть не тем, что вам требуется. Результатом этого копирования будет то, что указатели разных объектов будут ссылаться на одни и те же данные, а не на их независимые копии. Для фактического копирования данных следует сделать детальную копию. Детальное копирование может быть обеспечено на уровне языка или на уровне библиотек. В обычном C++ детальное копирование осуществляется на уровне языка с использованием конструктора копирования. В управляемом C++ оно осуществляется .NET Framework с помощью интерфейса Idoneable, который можно реализовывать в создаваемых классах специально для того, чтобы эти классы могли выполнять детальное копирование.
Следующими тремя вариантами исчерпываются возможные виды копирования, как для управляемых классов и структур, с использованием интерфейса Idoneable или без него, так и для неуправляемых классов и структур.
Пример программы
Проиллюстрируем изложенный материал программой CopyDemo. Эта программа делает копию экземпляра класса Course (Курс). В классе Course (Курс) содержится название курса и список (коллекция) студентов.
//Course .h
_gc class Course : public Idoneable
// класс сборщика мусора Курс: Idoneable
{
public:
String *pTitle;
ArrayList *pRoster;
Course(String *pTitle) // Курс
{
this->pTitle = pTitle; pRoster = new ArrayList;
}
void AddStudent(String *pName)
{
pRoster->Add(pName); // Добавить
}
void Show(String *pCaption) // Показать
{
Console::WriteLine("————{0}————", pCaption) ;
Console::WriteLine(
"Course : {0} with {1} students", // Курс: студенты
pTitle,
_box(pRoster->Count)); // Счет
lEnumerator *pEnum = pRoster->GetEnumerator();
while (pEnum->MoveNext())
{
String *pName =
dynamic_cast<String *>(pEnum->Current);
Console::WriteLine(pName);
}
}
Course *ShallowCopy() // Курс - поверхностная копия
{
return dynamic_cast<Course *>(MemberwiseClone());
}
Object *Clone()
{
Course *pCourse = new Course(pTitle); // новый Курс
pCourse->pRoster =
dynamic_cast<ArrayList *>(pRoster->Clone()); // Клон
return pCourse;
}
};
Тестовая программа создает экземпляр класса Course (Курс), называющийся pCl, a затем разными способами создает его копии с именем рС2.
//CopyDemo.h
_gc class CopyDemo
// класс сборщика мусора CopyDemo
{
private: // частный
static Course *pCl, *pC2; // статический Курс
public:
static void Main()
{
Console::WriteLine("Copy is done via pC2 = pCl");
// Копия, сделанная через рС2 = pCl
InitializeCourse ();
pCl->Show("original"); // Показать ("оригинал");
pC2 = pCl;
pC2->Show("copy"); // Показать ("копия");
pC2->pTitle = ".NET Programming"; // Программирование
на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student");
// Показать ("копия с измененным названием
//и новым студентом");
pCl->Show("original"); // Показать ("оригинал");
Console::WriteLine(
"\nCopy is done via pC2 = pCl->ShallowCopy()")
;
InitializeCourse ();
pC2 = pCl->ShallowCopy();
pC2->pTitle = ".NET Programming"; // Программирование
на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student"};
// Показать ("копия с измененным названием и новым
// студентом");
pCl->Show("original"); // Показать ("оригинал");
Console::WriteLine(
"\nCopy is done via pC2 = pCl->Clone()");
InitializeCourse ();
pC2 = dynamic_oast<Course *>(pCl->Clone());
pC2->pTitle = ".NET Programming"; // Программирование
//на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student");
// Показать ("копия с измененным названием
// новым студентом");
pCl->Show("original"); // Показать ("оригинал");
}
private: // частный
static void InitializeCourse()
{
pCl = new Course("Intro to Managed C++");
// новый Курс ("Введение в Управляемый С ++ ");
pCl->AddStudent("John"); // Джон
pCl->AddStudent("Mary"); // Мэри
}
};
Вот выдача программы:
Copy is done via pC2 = pCl
————original-----
Course : Intro to Managed C++ with 2 students
John
Mary
————copy———
Course : Intro to Managed C++ with 2 students
John
Mary
—----copy with changed title and new student-----
Course : .NET Programming with 3 students
John
Mary
Charlie
——--original-----
Course : .NET Programming with 3 students
John
Mary
Charlie
Copy is done via pC2 = pCl->ShallowCopy()
---—copy with changed title and new student--—-
Course : .NET Programming with 3 students
John
Mary
Charlie
-----original-----
Course : Intro to Managed C++ with 3 students
John
Mary
Charlie
Copy is done via pC2 = pCl->Clone()
--—-copy with changed title and new student——--
Course : .NET Programming with 3 students
John
Mary
Charlie
- -—original- —— -
Course : Intro to Managed C++ with 2 students
John
Mary
А вот и перевод выдачи1**:
Копия сделана через рС2 = рС1
-----оригинал-----
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
-----копия————
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
-----копия с измененным названием и новым студентом-----
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
- ——оригинал—- —
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
Копия сделана через рС2 = рС1-> ShallowCopy ()
-----копия с измененным названием и новым студентом-----
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
-—-оригинал---—
Добавлен, естественно, редактором русского перевода. — Прим.
ред.
Курс: Введение в Управляемый С ++ с 3 студентами
Джон
Мэри
Чарли
Копия сделана через рС2 = рС1-> Clone ()
-——копия с измененным названием и новым студентом—---
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
-----оригинал-----
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
Копирование указателей с помощью присваивания
Первым способом копирования является просто присваивание рС2=рС1. В этом случае мы получаем два указателя на один и тот же объект, и изменения, произведенные с использованием первого указателя, можно будет обнаружить в данных, адресуемых вторым указателем.
_gc class CopyDemo
// класс сборщика мусора CopyDemo
{
public:
static void Main()
{
Console::WriteLine("Copy is done via pC2 = pCl");
// Копия сделана через рС2 = pCl
InitializeCourse();
pCl->Show("original"); // Показать ("оригинал");
pC2 = pCl;
pC2->Show("copy"); // Показать ("копия");
pC2->pTitle = ".NET Programming"; // Программирование
на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student"};
// Показать ("копия с измененным названием
//и новым студентом");
pCl->Show("original"); // Показать ("оригинал");
Экземпляр класса Course (Курс) инициализируется методом InitializeCourse, причем в качестве названия курса выбирается "Intro to Managed C++" (Введение в управляемый C++) и на курс записаны два студента. Затем выполняется присваивание рС2=рС1, и с помощью указателя рС2 изменяется заголовок и добавляется новый студент. Затем демонстрируется, что произведенные изменения видны с помощью обоих указателей. Приведем результат работы первой части программы: Copy is done via pC2 = pCl
--original-----
Course : Intro to Managed C++ with 2 students
John
Mary
-copy-——-
Course : Intro to Managed C++ with 2 students
John
Mary
-copy with changed title and new student-----
Course : .NET Programming with 3 students
John
Mary
Charlie
-----original-----
Course : .NET Programming with 3 students
John
Mary
Charlie
А вот и перевод выдачи:
Копия сделана через рС2 = рС1
-----оригинал--——
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
-----копия-----
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
-----копия с измененным названием и новым студентом-----
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
-----оригинал-----
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
Почленное копирование
Теперь проиллюстрируем почленное копирование, выполненное с помощью метода MemberwiseClone класса Object (Объект). Так как этот метод защищен (имеет спецификатор доступа protected), он может быть вызван непосредственно только из экземпляра класса Course (Курс). Поэтому в классе Course (Курс) мы определили метод ShallowCopy, реализованный через метод MemberwiseClone.
_gc class Course : public ICloneable
// класс сборщика мусора Курс: ICloneable
{
Course *ShallowCopy()
{
return dynamic_cast<Course *>(MemberwiseClone());
}
};
Приведем вторую часть тестовой программы, в которой происходит вызов метода ShallowCopy. Так же, как и раньше, изменим с помощью второго указателя заголовок и добавим в список нового студента.
_gc class CopyDemo
// класс сборщика мусора CopyDemo
{
public:
static void Main() {
Console::WriteLine(
"\nCopy is done via pC2 = pCl->ShallowCopy()");
InitializeCourse();
pC2 = pCl->ShallowCopy();
pC2->pTitle = ".NET Programming"; // Программирование
на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student");
// Показать ("копия с измененным названием
// и новым студентом");
pCl->Show("original");
// Показать ("оригинал");
Ниже приведен результат работы второй части программы. Видно, что поле Title (Название), существует после копирования в двух независимых экземплярах, но коллекция Roster (Список), представляющая собой список студентов, была скопирована через указатель. Поэтому она одна для обеих копий, и изменения, внесенные с использованием одного указателя, видны через другой.
Copy is done via pC2 = pCl->ShallowCopy()
———-copy with changed title and new student—-—
Course : .NET Programming with 3 students
John
Mary
Charlie
—---original-----
Course : Intro to Managed C++ with 3 students
John
Mary
Charlie
А вот и перевод выдачи:
Копия сделана через рС2 = рС1-> ShallowCopy ()
—---копия с измененным названием и новым студентом-----
— Курс: Программирование на .МЕТ с 3 студентами Джон
Мэри
Чарли
-----оригинал-----
Курс: Введение в Управляемый С ++ с 3 студентами
Джон
Мэри
Чарли
Использование ICloneable
Последний способ копирования использует тот факт, что класс Course (Курс) поддерживает интерфейс ICloneable и реализует метод Clone (Клон). Для копирования коллекции Roster (Список) используется также то, что ArrayList (Список массивов) реализует интерфейс ICloneable, как было указано в этой главе ранее. Заметим, что метод Clone (Клон) возвращает значение типа Object*, так что перед тем, как присвоить его полю pRoster, возвращаемое значение необходимо привести к типу ArrayList*.
_gc class Course : public ICloneable
// класс сборщика мусора Курс: ICloneable
{
public:
Object *Clone()
{
Course *pCourse = new Course(pTitle); // новый Курс
pCourse->pRoster =
dynamic_cast<ArrayList *>(pRoster->Clone());
return pCourse;
}
};
Приведем третью часть программы, в которой вызывается метод Clone (Клон). Здесь мы тоже с помощью второго указателя изменяем название и добавляем в список нового студента.
_gc class CopyDemo
// класс сборщика мусора CopyDemo
{
public:
static void Main()
{
Console::WriteLine(
"\nCopy is done via pC2 = pCl->Clone()");
InitializeCourse ();
pC2 = dynamic_cast<Course *>(pCl->Clone());
pC2->pTitle = ".NET Programming"; // Программирование
на .NET
pC2->AddStudent("Charlie"); // Чарли
pC2->Show("copy with changed title and new student");
// Показать ("копия с измененным названием
//и новым студентом");
pCl->Shcw("01iginal");
// Показать ("оригинал");
Приведем выдачу третьей части программы. Теперь видно, что в результате копирования мы получили два независимых экземпляра класса Course (Курс), каждый из которых имеет свой заголовок и список студентов.
Copy is done via рС2 = pCl->Clone()
—---copy with changed title and new student-—--
Course : .NET Programming with 3 students
John
Mary
Charlie
—---original--——
Course : Intro to Managed C++ with 2 students
John
Mary
А вот и перевод выдачи:
Копия сделана через рС2 = рС1-> Clone О
—---копия с измененным названием и новым студентом-—--
Курс: Программирование на .NET с 3 студентами
Джон
Мэри
Чарли
—----оригинал-----
Курс: Введение в Управляемый С ++ с 2 студентами
Джон
Мэри
Последний подход иллюстрирует природу интерфейсов .NET. Вы можете копировать объекты, не особо задумываясь и не беспокоясь об их типах.